using RealtimeCollabGame.Domain.Events;
using RealtimeCollabGame.Domain.Exceptions;
using RealtimeCollabGame.Domain.ValueObjects;
using RealtimeCollabGame.Domain.Enums;

namespace RealtimeCollabGame.Domain.Entities;

public class Game : BaseEntity
{
    public string Name { get; private set; } = string.Empty;
    public string Code { get; private set; } = string.Empty;
    public string? Password { get; private set; }
    public GameStatus Status { get; private set; }
    public int MaxPlayers { get; private set; }
    public int GameDuration { get; private set; } // 游戏时长（秒）
    public DateTime? StartedAt { get; private set; }
    public DateTime? EndedAt { get; private set; }
    public Canvas Canvas { get; private set; } = null!;
    public Guid? HostId { get; private set; } // 房主ID

    private readonly List<Player> _players = new();
    private readonly List<IDomainEvent> _domainEvents = new();
    private readonly List<PaintAction> _paintActions = new();

    public IReadOnlyList<Player> Players => _players.AsReadOnly();
    public IReadOnlyList<PaintAction> PaintActions => _paintActions.AsReadOnly();
    public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    private Game() { }

    public Game(string name, string code, int maxPlayers = 6, int gameDuration = 180, string? password = null)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
        Code = code ?? throw new ArgumentNullException(nameof(code));
        Password = password;
        MaxPlayers = maxPlayers;
        GameDuration = gameDuration;
        Status = GameStatus.Waiting;
        Canvas = new Canvas(800, 600); // 默认画布大小
    }

    public void AddPlayer(Player player, bool isHost = false)
    {
        if (Status != GameStatus.Waiting)
            throw new DomainException("游戏已开始，无法添加玩家");

        if (_players.Count >= MaxPlayers)
            throw new DomainException("房间已满，无法添加更多玩家");

        if (_players.Any(p => p.Id == player.Id))
            throw new DomainException("玩家已在房间中");

        // 如果是房主，设置房主信息
        if (isHost)
        {
            HostId = player.Id;
            player.SetHost(true);
        }

        _players.Add(player);
        AddDomainEvent(new PlayerJoinedEvent(Id, player.Id, player.NickName));
    }

    public void RemovePlayer(Guid playerId)
    {
        var player = _players.FirstOrDefault(p => p.Id == playerId);
        if (player == null)
            throw new DomainException("玩家不在房间中");

        _players.Remove(player);
        AddDomainEvent(new PlayerLeftEvent(Id, playerId));

        // 如果房主离开，将房主权限转移给第一个玩家
        if (playerId == HostId && _players.Count > 0)
        {
            var newHost = _players.First();
            HostId = newHost.Id;
            newHost.SetHost(true);
            AddDomainEvent(new HostChangedEvent(Id, newHost.Id, newHost.NickName));
        }

        if (_players.Count < 2 && Status == GameStatus.Playing)
        {
            EndGame();
        }
    }

    public void SetPlayerReady(Guid playerId, bool isReady)
    {
        var player = _players.FirstOrDefault(p => p.Id == playerId);
        if (player == null)
            throw new DomainException("玩家不在房间中");

        player.SetReady(isReady);
        AddDomainEvent(new PlayerReadyEvent(Id, playerId, isReady));
    }

    public void StartGame()
    {
        if (_players.Count < 2)
            throw new DomainException("至少需要2名玩家才能开始游戏");

        if (Status != GameStatus.Waiting)
            throw new DomainException("游戏状态不正确");

        Status = GameStatus.Playing;
        StartedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        // 为每个玩家分配随机颜色
        var colors = PlayerColor.GetDistinctColors(_players.Count);
        for (int i = 0; i < _players.Count; i++)
        {
            _players[i].UpdateColor(colors[i]);
        }

        AddDomainEvent(new GameStartedEvent(Id, _players, GameDuration));
    }

    public void EndGame()
    {
        if (Status != GameStatus.Playing)
            throw new DomainException("游戏状态不正确");

        Status = GameStatus.Finished;
        EndedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        // 计算最终得分
        var playerScores = _players.Select(p => new PlayerScore(p.Id, p.NickName, p.Color, p.AreaCount)).ToList();
        var gameResult = new GameResult(Id, playerScores, StartedAt, EndedAt);

        AddDomainEvent(new GameEndedEvent(Id, gameResult));
    }

    public void AddPaintAction(PaintAction paintAction)
    {
        if (Status != GameStatus.Playing)
            throw new DomainException("游戏未开始，无法添加涂色动作");

        _paintActions.Add(paintAction);
        AddDomainEvent(new PaintActionAddedEvent(Id, paintAction));
    }

    public void UpdatePlayerArea(Guid playerId, int areaCount)
    {
        var player = _players.FirstOrDefault(p => p.Id == playerId);
        if (player == null)
            throw new DomainException("玩家不在房间中");

        player.UpdateAreaCount(areaCount);
        AddDomainEvent(new PlayerAreaUpdatedEvent(Id, playerId, areaCount));
    }

    public void ProcessPaintAction(Guid playerId, Position position, BrushSize brushSize)
    {
        if (Status != GameStatus.Playing)
            throw new DomainException("游戏未开始或已结束");

        var player = _players.FirstOrDefault(p => p.Id == playerId);
        if (player == null)
            throw new DomainException("玩家不在房间中");

        if (!Canvas.CanPaint(position, brushSize))
            throw new DomainException("无法在此位置绘制");

        var paintAction = new PaintAction(Id, playerId, position, brushSize, player.Color);
        _paintActions.Add(paintAction);

        Canvas.Paint(position, brushSize, player.Color);

        AddDomainEvent(new PaintActionAddedEvent(Id, paintAction));
    }

    public bool HasPassword => !string.IsNullOrEmpty(Password);

    public bool ValidatePassword(string password)
    {
        return !HasPassword || Password == password;
    }

    public int GetTimeLeft()
    {
        if (Status != GameStatus.Playing || StartedAt == null)
            return 0;

        var elapsed = DateTime.UtcNow - StartedAt.Value;
        var timeLeft = GameDuration - (int)elapsed.TotalSeconds;
        return Math.Max(0, timeLeft);
    }

    private void AddDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

